home *** CD-ROM | disk | FTP | other *** search
/ Nebula 2 / Nebula Two.iso / SourceCode / Database / SimpleTableView-1 / DataTable.m < prev    next >
Text File  |  1995-06-12  |  18KB  |  711 lines

  1. // -------------------------------------------------------------------------------------
  2. //  DataTable
  3. //  This software is without warranty of any kind.  Use at your own risk.
  4. // -------------------------------------------------------------------------------------
  5.  
  6. #import <objc/objc.h>
  7. #import <appkit/appkit.h>
  8. #import <mach/mach.h>
  9. #import <dbkit/dbkit.h>
  10. #import <libc.h>
  11. #import <stdio.h>
  12. #import <string.h>
  13. #import <ctype.h>
  14. #import "DataTable.h"
  15.  
  16. // -------------------------------------------------------------------------------------
  17. // misc
  18. #define    KEY_COLUMN                0
  19.  
  20. // -------------------------------------------------------------------------------------
  21. // string macros
  22. #define    freeString(S)            { if (S) { free(S); S = (char*)nil; } }
  23.  
  24. // -------------------------------------------------------------------------------------
  25. // table macros
  26. #define    dataROWS                tableHandle->dataId
  27. #define    columnINFO                tableHandle->columnId
  28. #define    orderedINFO                tableHandle->reorderId
  29.  
  30. // -------------------------------------------------------------------------------------
  31. @interface DataTable(private)
  32. - _resetDisplayedColumnOrdering;
  33. @end
  34.  
  35. // -------------------------------------------------------------------------------------
  36. @implementation DataTable
  37. // -------------------------------------------------------------------------------------
  38.  
  39. // -------------------------------------------------------------------------------------
  40. // initialize connection with data table 
  41.  
  42. /* open table */
  43. - initFromFile:(const char*)fileName
  44. {
  45.     char    *p = fileName? rindex((char*)fileName, '/') : (char*)nil;
  46.  
  47.     /* init super */
  48.     [self init];
  49.     nullIndex = (id)-1;
  50.     delegate = (id)nil;
  51.     isModified = NO;
  52.     
  53.     /* initialize table handle */
  54.     tableHandle = (dataTable_t*)malloc(sizeof(dataTable_t));
  55.     memset(tableHandle, 0, sizeof(dataTable_t));
  56.     tableHandle->name = NXCopyStringBuffer(p? (p+1) : "Unknown"); 
  57.     tableHandle->access = fileName? NXCopyStringBuffer(fileName) : (char*)nil;
  58.     tableHandle->viewSize.width = tableHandle->viewSize.height = 0.0;
  59.     tableHandle->columnId = [[[List alloc] initCount:1] empty];
  60.     tableHandle->reorderId = (id)nil;
  61.     tableHandle->dataId = [[List allocFromZone:[self zone]] init];
  62.     tableHandle->date = [self timestamp];
  63.         
  64.     /* read column-info and data */
  65.     if (![self readTableColumns] || ![self readTableData])
  66.         NXLogError("Unable to load column/data for file %s", (fileName?fileName:"?"));
  67.     
  68.     /* return */
  69.     return self;
  70.     
  71. }
  72.  
  73. // -------------------------------------------------------------------------------------
  74. // set attributes
  75.  
  76. /* set delegate */
  77. - setDelegate:aDelegate
  78. {
  79.     delegate = aDelegate;
  80.     return self;
  81. }
  82.  
  83. /* return current delegate */
  84. - delegate
  85. {
  86.     return delegate;
  87. }
  88.  
  89. /* null index */
  90. - setNullIndex:index
  91. {
  92.     nullIndex = index;
  93.     return self;
  94. }
  95.  
  96. /* set number of rows */
  97. - setRows:(u_int)rowCount
  98. {
  99.     return [self notImplemented:_cmd];
  100. }
  101.  
  102. /* set number of columns */
  103. - setColumns:(u_int)colCount
  104. {
  105.     return [self notImplemented:_cmd];
  106. }
  107.  
  108. /* set display order for column */
  109. - setDisplayOrder:(int)order andWidth:(float)width forColumnIndex:(int)column
  110. {
  111.     BOOL            didEdit = NO;
  112.     dataColumn_t    *ci = [self columnInfoAt:column];
  113.     if (!ci) return (id)nil;
  114.     if (ci->displayOrder != order) {
  115.         ci->displayOrder = order;
  116.         ci->isHidden = (order >= 0)? NO : YES;
  117.         didEdit = YES;
  118.         if (tableHandle->reorderId) {
  119.             [tableHandle->reorderId free];
  120.             tableHandle->reorderId = (id)nil;
  121.         }
  122.     }
  123.     if ((width >= 0.0) && (ci->size != width)) {
  124.         ci->size = width;
  125.         didEdit = YES;
  126.     }
  127.     if (didEdit) [self setDocEdited:YES];
  128.     return self;
  129. }
  130.  
  131. // -------------------------------------------------------------------------------------
  132. // freeing resources
  133.  
  134. /* free columns */
  135. - _freeColumnInfo
  136. {
  137.     int    c;
  138.     for (c = 0; c < [columnINFO count]; c++) {
  139.         dataColumn_t *ci = [self columnInfoAt:c];
  140.         freeString(ci->keyTag);
  141.         freeString(ci->title);
  142.         freeString(ci->nilValue);
  143.         free(ci);
  144.     }
  145.     [columnINFO free];
  146.     columnINFO = (id)nil;
  147.     if (orderedINFO) {
  148.         [orderedINFO free];
  149.         orderedINFO = (id)nil;
  150.     }
  151.     return self;
  152. }
  153.  
  154. /* free a single row */
  155. - _freeRow:rowId
  156. {
  157.     int    c;
  158.     for (c = 0; c < [rowId count]; c++) {
  159.         dataEntry_t    *de = entryPTR(rowId, c);
  160.         freeString(de->value);
  161.         free(de);
  162.     }
  163.     return self;
  164. }
  165.  
  166. /* free data */
  167. - _freeData
  168. {
  169.     int    r;
  170.     for (r = 0; r < [dataROWS count]; r++) [self _freeRow:[dataROWS objectAt:r]];
  171.     [dataROWS freeObjects];
  172.     [dataROWS free];
  173.     dataROWS = (id)nil;
  174.     return self;
  175. }
  176.  
  177. /* free */
  178. - free
  179. {
  180.  
  181.     /* free table */
  182.     if (tableHandle) {
  183.         freeString(tableHandle->name);
  184.         freeString(tableHandle->access);
  185.         [self _freeData];
  186.         [self _freeColumnInfo];
  187.         free(tableHandle);
  188.     }
  189.  
  190.     /* free object */
  191.     return [super free];
  192.     
  193. }
  194.  
  195. // -------------------------------------------------------------------------------------
  196. // View size (handle by superclass, delegate, or other object)
  197.  
  198. /* set size */
  199. - setViewSize:(NXSize*)size
  200. {
  201.     tableHandle->viewSize = *size;
  202.     return self;
  203. }
  204.  
  205. /* get size */
  206. - (const NXSize*)viewSize
  207. {
  208.     if ((tableHandle->viewSize.width > 0.0) && (tableHandle->viewSize.height > 0.0))
  209.         return &(tableHandle->viewSize);
  210.     return (NXSize*)nil;
  211. }
  212.  
  213. // -------------------------------------------------------------------------------------
  214. // saving the table
  215.  
  216. /* check last modified date */
  217. - (BOOL)tableHasChanged
  218. {
  219.     struct stat        st;
  220.     
  221.     /* stat file */
  222.     if (!tableHandle->access) return NO;
  223.     if (stat((char*)tableHandle->access, &st) < 0) {
  224.         NXLogError("Unable to stat file %s", tableHandle->access);
  225.         return YES;
  226.     }
  227.  
  228.     /* return file changed status */
  229.     return tableHandle->date == st.st_mtime? NO : YES;
  230.     
  231. }
  232.  
  233. /* indicate table has been edited */
  234. - setDocEdited:(BOOL)flag
  235. {
  236.     isModified = flag;
  237.     if (delegate && (delegate != self) && [delegate respondsTo:@selector(setDocEdited:)])
  238.         [delegate setDocEdited:flag];
  239.     return self;
  240. }
  241.  
  242. /* commit/save table */
  243. - commitTable
  244. {
  245.  
  246.     /* make sure we have a file name */
  247.     if (!tableHandle->access) {
  248.         // load table name */
  249.     }
  250.     
  251.     /* write entire table */
  252.     if ([self writeTable]) [self setDocEdited:NO];
  253.     
  254.     return self;
  255. }
  256.  
  257. // -------------------------------------------------------------------------------------
  258. // column info
  259.  
  260. /* return column info pointer */
  261. + (dataColumn_t*)_column:infoId infoAt:(int)n
  262. {
  263.     if (!infoId || (n < 0) || (n >= [infoId count])) return (dataColumn_t*)nil;
  264.     return (dataColumn_t*)[infoId objectAt:n];
  265. }
  266.  
  267. /* get column info by index */
  268. - (dataColumn_t*)columnInfoAt:(u_int)col
  269. {
  270.     return [[self class] _column:columnINFO infoAt:col];
  271. }
  272.  
  273. /* return column number for matching keyTag */
  274. - (int)indexForColumnName:(const char*)name
  275. {
  276.     int    c;
  277.     if (!name || !columnINFO) return -1;
  278.     for (c = 0; c < [columnINFO count]; c++) {
  279.         dataColumn_t *ci = [self columnInfoAt:c];
  280.         if (!strcmp(name, ci->keyTag)) return c;
  281.     }
  282.     return -1;
  283. }
  284.  
  285. /* reset displayed column ordering */
  286. - _resetDisplayedColumnOrdering
  287. {
  288.     int    c, cnt;
  289.  
  290.     /* empty reorder table */
  291.     if (!orderedINFO) orderedINFO = [[List alloc] initCount:1];
  292.     [orderedINFO empty];
  293.     
  294.     /* add visible column headers to new list */
  295.     for (c = 0, cnt = [columnINFO count]; c < cnt; c++) {
  296.         dataColumn_t *ci = [self columnInfoAt:c];
  297.         if (!ci || (ci->displayOrder < 0)) continue;
  298.         [orderedINFO addObject:(id)ci];
  299.     }
  300.  
  301.     /* sort reorder table by display order (since table is small, use simple-sort) */
  302.     for (c = 0, cnt = [orderedINFO count]; c < cnt; c++) {
  303.         int n;
  304.         dataColumn_t *ci = (dataColumn_t*)[orderedINFO objectAt:c];
  305.         for (n = c + 1; n < cnt; n++) {
  306.             dataColumn_t *ni = (dataColumn_t*)[orderedINFO objectAt:n];
  307.             if (ci->displayOrder > ni->displayOrder) {
  308.                 [orderedINFO replaceObjectAt:c with:(id)ni];
  309.                 [orderedINFO replaceObjectAt:n with:(id)ci];
  310.                 ci = ni;
  311.             }
  312.         }
  313.     }
  314.     
  315.     return self;
  316. }
  317.  
  318. /* get column info by order */
  319. - (dataColumn_t*)orderedColumnInfoAt:(u_int)ord
  320. {
  321.     if (!orderedINFO) [self _resetDisplayedColumnOrdering];
  322.     return [[self class] _column:orderedINFO infoAt:ord];
  323. }
  324.  
  325. /* add column info */
  326. - addColumnInfo:(dataColumn_t*)dc
  327. {
  328.     if (dc->type == CDT_UNKNOWN) dc->type = CDT_STRING;
  329.     if (!dc->title) dc->title = NXCopyStringBuffer(dc->keyTag);
  330.     if (dc->minSize <= 0.0) dc->minSize = dc->size;
  331.     [columnINFO addObject:(id)dc];
  332.     return self;
  333. }
  334.  
  335. // -------------------------------------------------------------------------------------
  336. // load text file table data
  337.  
  338. /* duplicate row */
  339. - newRowName:(const char*)rowN copyFromRow:(int)rowX
  340. {
  341.     int        c, rcnt = [dataROWS count], ccnt = [columnINFO count];
  342.     id        rowId, newRow;
  343.     if ((rowX < 0) || (rowX > rcnt)) rowX = rcnt;
  344.     rowId = rowX < rcnt? [dataROWS objectAt:rowX] : (id)nil;
  345.     newRow = [[[List alloc] initCount:0] empty];
  346.     if (![dataROWS insertObject:newRow at:MIN(rowX + 1, rcnt)]) {
  347.         NXLogError("Unable to add new row %s into table", rowN);
  348.         [newRow free];
  349.         return (id)nil;
  350.     }
  351.     for (c = 0; c < ccnt; c++) {
  352.         dataEntry_t *ne = entryNEW, *oe = rowId? entryPTR(rowId, c) : (dataEntry_t*)nil;
  353.         ne->value = (char*)[self copyStringValue:(c?(oe?oe->value:""):rowN) forColumn:c];
  354.         ne->isValid = -1;
  355.         [newRow insertObject:(id)ne at:c];
  356.     }
  357.     [self setDocEdited:YES];
  358.     return newRow;
  359. }
  360.  
  361. /* delete row */
  362. - deleteRowAt:(int)rowX
  363. {
  364.     id        rowId;
  365.     if ((rowX < 0) || (rowX >= [dataROWS count])) return (id)nil;
  366.     if (!(rowId = [dataROWS removeObjectAt:rowX])) return (id)nil;
  367.     [self _freeRow:rowId];
  368.     [self setDocEdited:YES];
  369.     return self;
  370. }
  371.  
  372. /* remove all table entries */
  373. - empty
  374. {
  375.     return self;
  376. }
  377.  
  378. /* fill/refill table */
  379. - reset
  380. {
  381.     return self;
  382. }
  383.  
  384. // -------------------------------------------------------------------------------------
  385. // sorting
  386.  
  387. /* sort comparison */
  388. - (int)sortCompare:(dataColumn_t*)dc values:(const char*)val1:(const char*)val2
  389. {
  390.     if (dc->type == CDT_INTEGER) return atoi(val1) - atoi(val2);
  391.     return strcasecmp(val1, val2);
  392. }
  393.  
  394. /* sort data table by column */
  395. #define    STRCOMP(C,T,R1,R2)    [self sortCompare:T values:entryVALUE(R1,C):entryVALUE(R2,C)]
  396. //#define    STRCOMP(C,T,R1,R2)    strcasecmp(entryVALUE(R1,C),entryVALUE(R2,C))
  397. - sortTableByColumn:(int)pri :(int)sec
  398. {
  399.     int                r, cnt;
  400.     dataColumn_t    *dcp, *dcs;
  401.     
  402.     /* check sort keys */
  403.     if (pri < 0) {
  404.         NXLogError("Invalid primary sort column %d", pri);
  405.         return (id)nil;
  406.     }
  407.     if (pri == KEY_COLUMN) sec = -1;
  408.     
  409.     /* get column type */
  410.     dcp = [self columnInfoAt:pri];    
  411.     dcs = (sec >= 0)? [self columnInfoAt:sec] : (dataColumn_t*)nil;    
  412.     
  413.     /* sort */
  414.     for (r = 0, cnt = [dataROWS count]; r < cnt; r++) {
  415.         int n;
  416.         id rowR = [dataROWS objectAt:r];
  417.         for (n = r + 1; n < cnt; n++) {
  418.             id rowN = [dataROWS objectAt:n];
  419.             int cmp = STRCOMP(pri,dcp,rowR,rowN);
  420.             if ((cmp > 0) || ((cmp == 0) && dcs && (STRCOMP(sec,dcs,rowR,rowN) > 0))) {
  421.                 [dataROWS replaceObjectAt:r with:rowN];
  422.                 [dataROWS replaceObjectAt:n with:rowR];
  423.                 rowR = rowN;
  424.             }
  425.         }
  426.     }
  427.     
  428.     return self;
  429. }
  430. #undef STRCOMP
  431.  
  432. /* sort data table by column */
  433. - sortTableByColumnName:(const char*)priName :(const char*)secName
  434. {
  435.     int        pri = [self indexForColumnName:priName];
  436.     int        sec = secName? [self indexForColumnName:secName] : -1;
  437.     
  438.     /* check sort keys */
  439.     if (pri < 0) {
  440.         NXLogError("Invalid primary sort key %s", (priName?priName:"?"));
  441.         return (id)nil;
  442.     }
  443.     if (secName && (sec < 0)) {
  444.         NXLogError("Invalid secondary sort key %s", secName);
  445.         return (id)nil;
  446.     }
  447.     
  448.     /* sort */
  449.     return [self sortTableByColumn:pri:sec];
  450.     
  451. }
  452.  
  453. // -------------------------------------------------------------------------------------
  454. // validate entries
  455.  
  456. /* validate specific entry */
  457. - (BOOL)_validateEntry:(dataEntry_t*)de forColumn:(u_int)column
  458. {
  459.     if (!de) return NO;
  460.     if (de->isValid < 0) {
  461.         dataColumn_t *dc = [self columnInfoAt:column];
  462.         if (dc->nilValue&&(!*de->value||!strcmp(de->value,dc->nilValue))) de->isValid = YES;
  463.         else de->isValid = [self verifyValue:de->value dataType:dc->type]? 1 : 0;
  464.     }
  465.     return de->isValid;
  466. }
  467.  
  468. /* validate entries until an error is found */
  469. - (BOOL)hasVerificationErrors
  470. {
  471.     int        r, rcnt = [dataROWS count];
  472.     for (r = 0; r < rcnt; r++) {
  473.         id rowId = [dataROWS objectAt:r];
  474.         int c, ccnt = [rowId count];
  475.         for (c = 0; c < ccnt; c ++) {
  476.             dataEntry_t *de = entryPTR(rowId,c);
  477.             if (![self _validateEntry:de forColumn:c]) return YES;
  478.         }
  479.     }
  480.     return NO;
  481. }
  482.  
  483. /* validate entry at specific location */
  484. - (BOOL)verifyValueAt:(u_int)row :(u_int)column
  485. {
  486.     return [self _validateEntry:[self entryAtIndex:row:column] forColumn:column];
  487. }
  488.  
  489. // -------------------------------------------------------------------------------------
  490. // table access
  491.  
  492. /* find row for specified name */
  493. - (int)indexForRowName:(const char*)rowN exactMatch:(BOOL)exact
  494. {
  495.     int        r;
  496.     if (!rowN) return -1; // not found
  497.     for (r = 0; r < [dataROWS count]; r++) {
  498.         id rowId = [dataROWS objectAt:r];
  499.         char *rn = (char*)entryVALUE(rowId,0);
  500.         if (exact) { if (!strcmp(rn, rowN)) return r; }
  501.         else       { if (!strstr(rn, rowN)) return r; }
  502.     }
  503.     return -1;
  504. }
  505.  
  506. /* return specified table entry (by index) */
  507. - (dataEntry_t*)entryAtIndex:(u_int)rowX :(u_int)colX
  508. {
  509.     id    rowId = [dataROWS objectAt:rowX];
  510.     if (!rowId || (colX > [rowId count])) return (dataEntry_t*)nil;
  511.     return entryPTR(rowId,colX);
  512. }
  513.  
  514. /* return specified table entry (by name) */
  515. - (const char*)valueForRowName:(const char*)rowN columnName:(const char*)colN
  516. {
  517.     int    wc = [self indexForColumnName:colN];
  518.     if (wc >= 0) {
  519.         int r = [self indexForRowName:rowN exactMatch:YES];
  520.         if (r >= 0) return entryVALUE([dataROWS objectAt:r], wc);
  521.     }
  522.     return (char*)nil;
  523. }
  524.  
  525. /* return specified table entry (by index) */
  526. - (const char*)valueAtIndex:(u_int)rowX :(u_int)colX
  527. {
  528.     dataEntry_t        *de = [self entryAtIndex:rowX:colX];
  529.     return de? de->value : (char*)nil;
  530. }
  531.  
  532. /* return string constant at location */
  533. - (const char*)valueFor:(u_int)rowIndex :(u_int)columnIndex
  534. {
  535.     return [self valueAtIndex:rowIndex:columnIndex];
  536. }
  537.  
  538. /* set indexed table entry */
  539. - setValue:(const char*)value atIndex:(u_int)rowX :(u_int)colX
  540. {
  541.     dataEntry_t        *de = [self entryAtIndex:rowX:colX];
  542.     if (!de) return (id)nil;
  543.     freeString(de->value);
  544.     de->value = (char*)[self copyStringValue:value forColumn:colX];
  545.     de->isValid = -1;
  546.     [self setDocEdited:YES];
  547.     return self;
  548. }
  549.  
  550. /* set value for specified row/column name */
  551. - setValue:(const char*)value forRowName:(const char*)rowN columnName:(const char*)colN
  552. {
  553.     int    wc = [self indexForColumnName:colN];
  554.     if (wc >= 0) {
  555.         int wr = [self indexForRowName:rowN exactMatch:YES];
  556.         if (wr >= 0) return [self setValue:value atIndex:wr:wc];
  557.     }
  558.     return (id)nil;
  559. }
  560.  
  561. /* change table value (note: index is independent of actual column position) */
  562. - setValueFor:rowIndex :colIndex from:aValue
  563. {
  564.     return [self setValue:[aValue stringValue] atIndex:(u_int)rowIndex:(u_int)colIndex];
  565. }
  566.  
  567. /* change table value (note: index is independent of actual column position) */
  568. - setValueFor:colIndex at:(u_int)rowPosition from:aValue
  569. {
  570.     return [self setValue:[aValue stringValue] atIndex:rowPosition:(u_int)colIndex];
  571. }
  572.  
  573. /* get table value */
  574. - getValueFor:rowIndex :columnIndex into:aValue
  575. {
  576.     return [self getValueFor:columnIndex at:(u_int)rowIndex into:aValue];
  577. }
  578.  
  579. /* get table value */
  580. - getValueFor:index at:(u_int)aPosition into:aValue
  581. {
  582.     if (index == nullIndex) { [aValue setNull]; return self; }
  583.     [aValue setStringValue:(char*)[self valueFor:aPosition :(u_int)index]];
  584.     return self;
  585. }
  586.  
  587. /* return specified table entry (by name) */
  588. - (int)scanForValue:(const char*)value inColumnName:(const char*)colN
  589.     startingAtRow:(int)rowX backwards:(BOOL)back
  590. {
  591.     if (value && colN) {
  592.         int wc = [self indexForColumnName:colN];
  593.         if (wc >= 0) {
  594.             int r = rowX < 0? [dataROWS count] : rowX % [dataROWS count];
  595.             if (rowX < 0) r = [dataROWS count];
  596.             else r = rowX % [dataROWS count];
  597.             for (;;) {
  598.                 id rowId = [dataROWS objectAt:r];
  599.                 if (strstr(entryVALUE(rowId, wc), value)) return r;
  600.                 r = (r + 1) % [dataROWS count];
  601.                 if (r == rowX) break;
  602.             }
  603.         }
  604.     }
  605.     return -1;
  606. }
  607.  
  608. // -------------------------------------------------------------------------------------
  609. // return table attributes
  610.  
  611. /* return name of table */
  612. - (const char*)tableName
  613. {
  614.     return tableHandle->name;
  615. }
  616.  
  617. /* return title of table */
  618. - (const char*)tableTitle
  619. {
  620.     return tableHandle->name;
  621. }
  622.  
  623. /* return access(path) of table */
  624. - (const char*)tableAccess
  625. {
  626.     return tableHandle->access;
  627. }
  628.  
  629. /* return number of visible columns in table */
  630. - (u_int)visibleColumnCount
  631. {
  632.     if (!orderedINFO) [self _resetDisplayedColumnOrdering];
  633.     return [orderedINFO count];
  634. }
  635.  
  636. /* return number of visible columns */
  637. - (u_int)columnCount
  638. {
  639.     return [self visibleColumnCount];
  640. }
  641.  
  642. /* return number of visible columns in table */
  643. - (u_int)actualColumnCount
  644. {
  645.     return [columnINFO count];
  646. }
  647.  
  648. /* return number of rows in table */
  649. - (u_int)rowCount
  650. {
  651.     return [dataROWS count];
  652. }
  653.  
  654. // -------------------------------------------------------------------------------------
  655. // DBTableView notification methods
  656.  
  657. /* user selection changed */
  658. - tableViewDidChangeSelection:aTableView
  659. {
  660.     return self;
  661. }
  662.  
  663. /* user move column */
  664. - tableView:aTableView movedColumnFrom:(u_int)oldpos to:(u_int)newpos
  665. {
  666.     [self setDocEdited:YES];
  667.     return self;
  668. }
  669.  
  670. /* selection will change */
  671. - (BOOL)tableViewWillChangeSelection:aTableView
  672. {
  673.     return YES;    // allow selection change
  674. }
  675.  
  676. // -------------------------------------------------------------------------------------
  677. // subclass methods
  678.  
  679. - (time_t)timestamp
  680. {
  681.     return (time_t)0;
  682. }
  683.  
  684. - readTableColumns
  685. {
  686.     return [self subclassResponsibility:_cmd];
  687. }
  688.  
  689. - readTableData
  690. {
  691.     return [self subclassResponsibility:_cmd];
  692. }
  693.  
  694. - writeTable
  695. {
  696.     return [self subclassResponsibility:_cmd];
  697. }
  698.  
  699. - (const char*)copyStringValue:(const char*)value forColumn:(int)index
  700. {
  701.     return NXCopyStringBuffer(value? value : "");
  702. }
  703.  
  704. - (BOOL)verifyValue:(const char*)value dataType:(int)dataType
  705. {
  706.     return value? YES : NO;
  707. }
  708.  
  709. // -------------------------------------------------------------------------------------
  710. @end 
  711.